
package com.bangcommunity.bbframe.sdm.web.shiro.stateless;

import com.bangcommunity.bbframe.common.base.BaseResult;
import com.bangcommunity.bbframe.common.base.ResultCode;
import com.bangcommunity.bbframe.common.utils.lang.StringUtils;
import com.bangcommunity.bbframe.sdm.utils.JacksonUtil;
import com.bangcommunity.bbframe.sdm.utils.RequestUtils;
import com.bangcommunity.bbframe.sdm.web.exception.RestConf;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.net.URLEncoder;

/**
 * 基于Shiro 实现的SSO登陆认证的Filter 用于拦截所有需要登录状态访问的url以及判断登录请求执行登录。
 * 1、此处判断是否登录状态除了默认的shiro中的session判断外,增加了对session中存储的uid和token的校验。 从而保证token失效之后,登录状态失效。
 * token&uid的校验根据子应用实现 2、判断登录请求使用shiro默认判断login url,或者是参数(url、header、cookie)中包含SSO_TOKEN参数。
 * 3、获取SSO_TOKEN并使用该token构建登录信息供shiro执行登录。子类扩展需要构造的登录信息。
 * 4、子应用需要配置app.session.login.expire.ms用于控制登录状态的过期时间。-1表示永不过期。
 * 
 * @version 1.0
 * @author tanghc
 */
public abstract class AbstractSdmStatelessAuthenticationFilter<PK extends Serializable, TK extends AuthenticationToken>
        extends FormAuthenticationFilter {

    protected Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        String tokenValue = RequestUtils.extractParam((HttpServletRequest) request, RestConf.tokenKey);
        boolean hasToken = StringUtils.isNotBlank(tokenValue);
        if (hasToken) {
            if (logger.isTraceEnabled()) {
                logger.trace("Login submission detected.  Attempting to execute login.");
            }
            boolean executeLogin = executeLogin(request, response);
            return executeLogin;
        } else {
            if (logger.isTraceEnabled()) {
                logger.trace("Attempting to access a path which requires authentication.  Forwarding to the "
                        + "Authentication url [" + getLoginUrl() + "]");
            }
            onNotLogin((HttpServletRequest) request, (HttpServletResponse) response);
            return false;
        }
    }
    protected boolean hasPermition( ServletRequest request, ServletResponse response){
        return true;
    }
    protected void onNotPermit(AuthenticationToken token, Subject subject, HttpServletRequest request, HttpServletResponse response){
        try {
            boolean ajaxRequest = RequestUtils.isAjaxRequest(request);
            if (ajaxRequest || RestConf.isRest) {
                BaseResult result = BaseResult.fail(ResultCode.NOTALLOWED.getCode(), "没有权限访问");
                HttpServletResponse res = response;
                res.setHeader("Content-Type", "application/json;charset=utf-8");
                PrintWriter out = res.getWriter();
                JacksonUtil jsonMapper = new JacksonUtil();
                String s = jsonMapper.toJson(result);
                logger.info("未登录,{}", request.getRequestURI());
                out.write(s);
                out.flush();
                out.close();
            } else {
                if (isLoginRequest(request, response)) {
                    super.redirectToLogin(request, response);
                } else {
                    HttpServletRequest httpServletRequest = (HttpServletRequest) request;
                    String queryString = StringUtils.trimToEmpty(httpServletRequest.getQueryString());
                    String callbackUrl = httpServletRequest.getRequestURI()
                            + (StringUtils.isNotBlank(queryString) ? "?" + queryString : "");

                    String redirectUrl = super.getLoginUrl() + "?url=" + URLEncoder.encode(callbackUrl, "UTF-8");
                    WebUtils.issueRedirect(request, response, redirectUrl);
                }

            }
        } catch (IOException e) {
            logger.error("onNotPermit response exceptioon: {}", e);
        }
    }

    /**
     * 基于request和response生成ssotoken
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {

        try {
            return createSdmToken((HttpServletRequest) request, (HttpServletResponse) response);
        } catch (Exception e) {
            logger.error("createToken exception: ", e);
        }
        return null;
    }

    protected void onNotLogin(HttpServletRequest request, HttpServletResponse response) {
        try {
            boolean ajaxRequest = RequestUtils.isAjaxRequest(request);
            if (ajaxRequest || RestConf.isRest) {
                BaseResult result = BaseResult.fail(ResultCode.NOTLOGIN.getCode(), "登录信息有误");
                HttpServletResponse res = response;
                res.setHeader("Content-Type", "application/json;charset=utf-8");
                PrintWriter out = res.getWriter();
                JacksonUtil jsonMapper = new JacksonUtil();
                String s = jsonMapper.toJson(result);
                logger.info("未登录,{}", request.getRequestURI());
                out.write(s);
                out.flush();
                out.close();
            } else {
                if (isLoginRequest(request, response)) {
                    super.redirectToLogin(request, response);
                } else {
                    HttpServletRequest httpServletRequest = (HttpServletRequest) request;
                    String queryString = StringUtils.trimToEmpty(httpServletRequest.getQueryString());
                    String callbackUrl = httpServletRequest.getRequestURI()
                            + (StringUtils.isNotBlank(queryString) ? "?" + queryString : "");

                    String redirectUrl = super.getLoginUrl() + "?url=" + URLEncoder.encode(callbackUrl, "UTF-8");
                    WebUtils.issueRedirect(request, response, redirectUrl);
                }

            }
        } catch (IOException e) {
            logger.error("onNotLogin response exceptioon: {}", e);
        }
    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request,
            ServletResponse response) {

        logger.info("登录用户信息-onLoginFailure: {}", token, e);
        String tokenValue = RequestUtils.extractParam((HttpServletRequest) request, RestConf.tokenKey);
        boolean hasToken = StringUtils.isNotBlank(tokenValue);
        if (hasToken) {
            removeToken((HttpServletRequest) request, (HttpServletResponse) response);
            if (isLoginRequest(request, response)) {
                return true;
            }
        }

        onNotLogin((HttpServletRequest) request, (HttpServletResponse) response);

        return false;
    }

    protected void removeToken(HttpServletRequest request, HttpServletResponse response) {

    }

    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
            ServletResponse response) throws Exception {
        logger.info("登录用户信息-onLoginSuccess");
        boolean hasPermition = hasPermition(request, response);
        if(!hasPermition){
            logger.info("登录用户信息-没有权限");
            onNotPermit( token, subject, (HttpServletRequest) request, (HttpServletResponse) response);
        }
        return hasPermition;
    }

    protected abstract TK createSdmToken(HttpServletRequest request, HttpServletResponse response);

}
